﻿#region Using directives

using System;
using System.IO;
using System.Collections.Generic;
using System.Xml;
using System.Text;

using Weazel.Gezel.Parser;

#endregion

namespace Weazel.Gezel.Simulator.Reference
{
  /// <summary>
  /// Enumerates the possible simulation outcomes
  /// </summary>
  public enum SimulationResultType
  {
    /// <summary>
    /// Simulation terminated without errors
    /// </summary>
    Success,
    /// <summary>
    /// Simulation terminated with warning(s)
    /// </summary>
    Warning,
    /// <summary>
    /// Simulation by an exception
    /// </summary>
    Failure,
  }

  /// <summary>
  /// Encapsulates the data produced by a simulation run
  /// </summary>
  public class SimulationResult
  {
    private readonly Exception exception;
    /// <summary>
    /// The exception encountered or null
    /// if no exception.
    /// </summary>
    /// <value></value>
    public Exception Exception
    {
      get { return exception; }
    }

    private readonly SimulationResultType resultType;
    /// <summary>
    /// Was the simulation successfull or not?
    /// </summary>
    /// <value></value>
    public SimulationResultType ResultType
    {
      get { return resultType; }
    }

    /// <summary>
    /// Human readable explanation
    /// of the simulation result
    /// </summary>
    /// <value></value>
    public string Message
    {
      get
      {
        return getMessage();
      }
    }

    private readonly TimeSpan elapsed;
    public TimeSpan Elapsed
    {
      get { return elapsed; }
    }

    /// <summary>
    /// Number of cycles run
    /// </summary>
    private readonly long cycle;
    public long Cycle
    {
      get { return cycle; }
    }

    /// <summary>
    /// Creates a new <see cref="SimulationResult"/> instance.
    /// </summary>
    /// <param name="cycle">number of cycles run</param>
    public SimulationResult(TimeSpan elapsed, long cycle)
    {
      this.elapsed = elapsed;
      this.cycle = cycle;
      this.resultType = SimulationResultType.Success;
    }

    /// <summary>
    /// Creates a new <see cref="SimulationResult"/> instance.
    /// </summary>
    /// <param name="cycle">number of cycles run</param>
    /// <param name="exception">exception that caused the failure</param>
    public SimulationResult(TimeSpan elapsed, long cycle, Exception exception)
    {
      this.elapsed = elapsed;
      this.exception = exception;
      this.cycle = cycle;
      this.resultType = SimulationResultType.Failure;
    }

    private string getMessage()
    {
      if (exception == null)
      {
        return string.Format("Time elapsed: {0} ms", elapsed.TotalMilliseconds);
      }
      else if (exception is CombinatorialLoopException)
      {
        CombinatorialLoopException cle = exception as CombinatorialLoopException;
        StringBuilder buffer = new StringBuilder();
        buffer.AppendLine(cle.Message);

        if (cle == null || cle.Signals == null)
        {
            buffer.Append("Unable to evaluate combinatorial loop trace");
        }
        else
        {
            foreach (AbstractSignal signal in cle.Signals)
            {
                // dont print signals defined for internal purposes
                if (signal is Signal && !(signal as Signal).IsUserDefined)
                    continue;

                buffer.AppendFormat("  through '{0}' ({1})\n", signal.Name, signal.GetType().Name);
            }
        }

        return string.Format("Simulation aborted in cycle {0} with the following error:\n\n{1}\n",
          cycle,
          buffer.ToString()
        );
      }
      else if (exception is ParseException)
      {
        ParseException ex = exception as ParseException;
        return string.Format("Simulation aborted in cycle {0} with a parse error:\n\n{1}\n",
                cycle,
                ex.Message
              );
      }
      else if (exception is IpBlockFileNotFoundException)
      {
        IpBlockFileNotFoundException ex = exception as IpBlockFileNotFoundException;
        return string.Format("Simulation aborted in cycle {0} with the following ipblock error:\n\n{1}\n",
                cycle,
                ex.Message
              );
      }
      else if (exception is IpBlockIndexOutOfRangeException)
      {
        IpBlockIndexOutOfRangeException ex = exception as IpBlockIndexOutOfRangeException;
        return string.Format("Simulation aborted in cycle {0} with the following ipblock error:\n\n{1}\n",
                cycle,
                ex.Message
              );
      }
      else if (exception is SignalDefinerException)
      {
        return string.Format("Simulation aborted in cycle {0} with the following signal error:\n\n{1}\n",
          cycle,
          exception.Message
        );
      }
			else if (exception is ExpressionEvaluationException)
			{
				return string.Format("Simulation aborted in cycle {0} with the following expression error:\n\n{1}\n",
					cycle,
					exception.Message
				);
			}
      else
      {
        SimulationResult.TryLogException(exception);

        return string.Format("Simulation aborted in cycle {0} with an unexpected error:\n\n{1}\n",
          cycle,
          exception.Message
        );
      }
    }

    /// <summary>
    /// Utility routine that writes an 
    /// exception to a log file on disk
    /// using xml serialization of the
    /// exception object.
    /// </summary>
    /// <param name="e">The exception to be logged</param>
    public static void TryLogException(Exception e)
    {
      string filename = null;
      try
      {
        Random rand = new Random();

        // try filenames until one that does not
        // exist is found
        do
        {
          filename = string.Format("exception_{0}.log", rand.Next(0, 999999).ToString().PadLeft(6, '0'));
        } while (File.Exists(filename));

        XmlDocument doc = new XmlDocument();
        doc.CreateXmlDeclaration("1.0", "UTF-8", string.Empty);

        XmlElement root = doc.CreateElement("Exception");
        doc.AppendChild(root);

        XmlElement type = doc.CreateElement("Type");
        type.InnerText = e.GetType().Name;
        root.AppendChild(type);

        XmlElement message = doc.CreateElement("Message");
        message.InnerText = e.Message;
        root.AppendChild(message);

        XmlElement stacktrace = doc.CreateElement("StackTrace");
        stacktrace.InnerText = e.StackTrace;
        root.AppendChild(stacktrace);

        using (StreamWriter writer = new StreamWriter(filename))
        {
          doc.Save(writer);
        }
        Console.Error.WriteLine("Exception logged to file '{0}'", filename);
      }
      catch
      {
        Console.Error.WriteLine("Failed to log exception to file");
      }      
    }
  }
}
